Proplem:

We live in a time when video games are extremely popular. The global video game market continues to grow year-on-year, and the industry is now valued at over $100 billion worldwide. With technology continuously pushing the boundaries, video games have only become more popular and more high-quality. Gameplay mechanics, cutting-edge graphics, and intricate storylines make today’s games more immersive than ever before. We chose this dataset to gain insights on the popularity of different gaming platforms and the most successful genres associated with those platforms.

Data Mining Task:

Our task data mining task is to predict the popularity of upcoming games using regression.

Description of the dataset:

The dataset provided by vgchartz.com supply us with a valuable resource to explore the platforms and genres of the top 16599 global video games. Through it, we can analyze the most popular platforms and genres that are influencing global sales, and detectr how regions’ sales affect global sales.

Our goal:

Our goal from studying this dataset is to utilize classification and clustering techniques on the input data to make predictions about the popularity of upcoming games.

Attributes description:

Attributes name Description Data type
Rank Ranking of the game based on global sales. Numeric
Name Name of the game. Nominal
Platform Platform the game was released on. Nominal
Year Year the game was released. Ordinal
Genre Genre of the game Nominal
Publisher Publisher of the game. Nominal
NA_Sales Sales of the game in North America Numeric (ratio-scaled)
EU_Sales Sales of the game in Europe Numeric (ratio-scaled)
JP_Sales Sales of the game in Japan Numeric (ratio-scaled)
Other_Sales Sales of the game in other regions Numeric (ratio-scaled)
Global_Sales Total sales of the game worldwide Numeric (ratio-scaled)

Class label:

Popular’ is our class label, we will use Global_Sales attribute to predict whether a game will sell 1000000 or more globally.

loading libraries needed for our data mining tasks:

library(outliers) 
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(Hmisc)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Attaching package: ‘Hmisc’

The following objects are masked from ‘package:dplyr’:

    src, summarize

The following objects are masked from ‘package:base’:

    format.pval, units
library(ggplot2)
library(mlbench)
library(caret)
Loading required package: lattice
options(max.print=9999999)

Importing our dataset:

dataset=read.csv("Dataset/vgsales.csv")

General info about our dataset:

cheking number of rows and columns, and cheking dimensionality and coulumns names

nrow(dataset)
ncol(dataset)
dim(dataset)
names(dataset)

Dataset structure:

str(dataset)

sample of raw dataset(first 10 rows):

head(dataset, 10)

sample of raw dataset(last 10 rows):

tail(dataset, 10)

summary of our dataset:

summary(dataset)

variance of numeric data:

var(dataset$NA_Sales)
var(dataset$EU_Sales)
var(dataset$JP_Sales)
var(dataset$Other_Sales)
var(dataset$Global_Sales)

Graphs:

dataset2 <- dataset %>% sample_n(50)
tab <- dataset2$Platform %>% table()
precentages <- tab %>% prop.table() %>% round(3) * 100 
txt <- paste0(names(tab), '\n', precentages, '%') 

pie(tab, labels=txt , main = "Pie chart of Platform") 

We notice from the pie chart of platform attribute that releasing a game for PS users will increase the popularity of the game since it is the most common platform among gamers.

# coloring barplot and adding text
tab<-dataset$Genre %>% table() 

precentages<-tab %>% prop.table() %>% round(3)*100 

txt<-paste0(names(tab), '\n',precentages,'%') 

bb <- dataset$Genre %>% table() %>% barplot(axisnames=F, main = "Barplot for Popular genres ",ylab='count',col=c('pink','blue','lightblue','green','lightgreen','red','orange','red','grey','yellow','azure','olivedrab')) 

text(bb,tab/2,labels=txt,cex=1.5) 

In terms of genre, action games are the most popular, followed by sports and music games. It is safe to assume that a high number of genres of this nature exist due to their popularity and sales.

boxplot(dataset$NA_Sales , main="
BoxPlot for NA_Sales")

The boxplot of the NA_Sales (Sales of the game in north America) attribute indicates that the values are close to each other ,and there are a lot of outliers since the dataset represents all the north America sales of video games.

boxplot(dataset$EU_Sales, main="
 BoxPlot for EU_Sales")

The boxplot of the EU_Sales (sales of the game in Europe) attribute indicates that the values are close to each other, and there are a lot of outliers since the dataset represents all the Europe sales of video games.

boxplot(dataset$JP_Sales , main="
 BoxPlot for JP_Sales")

The boxplot of the JP_Sales (sales of the game in Japan) attribute indicates that the values are close to each other, and there are a lot of outliers since the dataset represents all the Japan sales of video games.

boxplot(dataset$Other_Sales , main="
 BoxPlot for Other_Sales") 

The boxplot of the Other-sales attribute indicate that the values are close to each other ,and there is a lot of outliers since the dataset represents the global sales of video games.

boxplot(dataset$Global_Sales , main="BoxPlot for Global_Sales")

The boxplot of the Global-sales attribute indicate that the values are close to each other ,and there is a lot of outliers since the dataset represents the global sales of video games.

qplot(data = dataset, x=Global_Sales,y=Genre,fill=I("yellow"),width=0.5 ,geom = "boxplot" , main = "BoxPlots for genre and Global_Sales")

In the boxplot we can see that all the genres have Glob_ sales close to each other, but we notice an outlier that reaches more than 80 Glob_ sales which is a game with genre sports.

dataset$Year %>% table() %>% barplot( main = "Barplot for year")

By the barplot we can see that the number of video games were low from 1980 year until the 2000 numbers of games grow to more than 1200 till 2012.

pairs(~NA_Sales + EU_Sales + JP_Sales + Other_Sales + Global_Sales, data = dataset,
      main = "Sales Scatterplot")

We used Scatterplot to determine the type of correlation we have between the sales; we can see that the majority have positive correlation with each other.

(Pre - processing):

Varaible transformation:

dataset$Rank=as.character(dataset$Rank)

We transformed the Rank from numric to char,because we will use them as ordinal data.

Null checking:

we checked nulls values to know how many nulls values we have, so we can determine how we will deal with them.

sum(is.na(dataset$Rank))
NullRank<-dataset[dataset$Rank=="N/A",]
NullRank

checking for nulls in Rank (there is no nulls)

sum(is.na(dataset$Name))
NullName<-dataset[dataset$Name=="N/A",]
NullName

checking for nulls in name (there is no nulls)

sum(is.na(dataset$Platform))
NullPlatform<-dataset[dataset$Platform=="N/A",]

checking for nulls in Platform(there is no nulls)

sum(is.na(dataset$Year))
NullYear<-dataset[dataset$Year=="N/A",]
NullYear

checking for nulls in year we won’t delete the null and we will leave them as global constant because we want the sales data out of them.

sum(is.na(dataset$Genre))
NullGenre<-dataset[dataset$Genre=="N/A",]
NullGenre

checking for nulls in Genre(there is no nulls)

sum(is.na(dataset$Publisher))
NullPublisher<-dataset[dataset$Publisher=="N/A",]
NullPublisher

checking for nulls in Publisher. we won’t delete the null and we will leave them as global constant as it is because we want the sales data of them.

sum(is.na(dataset$NA_Sales))
NullNA_Sales<-dataset[dataset$NA_Sales=="N/A",]
NullNA_Sales

checking for nulls in NA_Sales (there is no nulls)

sum(is.na(dataset$EU_Sales))
NullEU_Sales<-dataset[dataset$EU_Sales=="N/A",]
NullEU_Sales

checking for nulls in EU_Sales (there is no nulls)

sum(is.na(dataset$JP_Sales))
NullJP_Sales<-dataset[dataset$JP_Sales=="N/A",]
NullJP_Sales

checking for nulls in JP_Sales (there is no nulls)

sum(is.na(dataset$Other_Sales))
NullOther_Sales<-dataset[dataset$Other_Sales=="N/A",]

There is no null values in the other_sales.

sum(is.na(dataset$Global_Sales))
NullGlobal_Sales<-dataset[dataset$Global_Saless=="N/A",]

There is no null values in the Global_Sales.

Encoding:

We will encode our categorical data since most machine learning algorithms work with numbers rather than text.

dataset$Platform=factor(dataset$Platform,levels=c("2600","3DO","3DS","DC","DS","GB","GBA","GC","GEN","GG","N64","NES","NG","PC","PCFX","PS","PS2","PS3","PS4","PSP","PSV","SAT","SCD","SNES","TG16","Wii","WiiU","WS","X360","XB","XOne"), labels=c(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31))

this column will be encoded to facilitate our data mining task.

dataset$Genre=factor(dataset$Genre,levels=c("Action","Adventure","Fighting","Platform","Puzzle","Racing","Role-Playing","Shooter","Simulation","Sports","Strategy","Misc"),labels=c(1,2,3,4,5,6,7,8,9,10,11,12))

Since most machine learning algorithms work with numbers and not with text or categorical variables, this column will be encoded to facilitate our data mining task.

Outliers:

Analyses and statistical models can be ruined by outliers, making it difficult to detect a true effect. Therefore, we are checking for them and removing them if we find any.

outlier of NA_Sales

OutNA_Sales = outlier(dataset$NA_Sales, logical =TRUE)
sum(OutNA_Sales)
[1] 1
Find_outlier = which(OutNA_Sales ==TRUE, arr.ind = TRUE)

outlier of EU_Sales

OutEU_Sales = outlier(dataset$EU_Sales, logical =TRUE)
sum(OutEU_Sales)
[1] 1
Find_outlier = which(OutEU_Sales ==TRUE, arr.ind = TRUE)

outlier of JP_Sales

OutJP_Sales = outlier(dataset$JP_Sales, logical =TRUE)
sum(OutJP_Sales)
[1] 1
Find_outlier = which(OutJP_Sales ==TRUE, arr.ind = TRUE)

outlier of other_sales

OutOS=outlier(dataset$Other_Sales, logical=TRUE)  
sum(OutOS)  
[1] 1
Find_outlier=which(OutOS==TRUE, arr.ind=TRUE)  

outlier of Global_sales

OutGS=outlier(dataset$Global_Sales, logical=TRUE)  
sum(OutGS)  
[1] 1
Find_outlier=which(OutGS==TRUE, arr.ind=TRUE)  

Remove outliers

dataset= dataset[-Find_outlier,]

Normalization:

The normalization of data will improve the performance of many machine learning algorithms by accounting for differences in the scale of the input features.

Dataset before normalization:

datsetWithoutNormalization<-dataset
normalize <- function(x) {return ((x - min(x)) / (max(x) - min(x)))}
dataset$NA_Sales<-normalize(datsetWithoutNormalization$NA_Sales)
dataset$EU_Sales<-normalize(datsetWithoutNormalization$EU_Sales)
dataset$JP_Sales<-normalize(datsetWithoutNormalization$JP_Sales)
dataset$Other_Sales<-normalize(datsetWithoutNormalization$Other_Sales)
dataset$Global_Sales<-normalize(datsetWithoutNormalization$Global_Sales)

We chose min-max normalization instead of z-score normalization because min-max transform the data into a specific range, which enhances its suitability for visualization and comparison. Additionally, it simplifies the process of assessing attribute importance and their contributions to the model.

Feautre selection:

Our class label (popular) refers to Global_Sales.because we have multiple regions sales we chose to evaluate each region sales based on their importance to (global_sales) column,and those that are less important will be deleted from the dataset.

Use roc_curve area as score

roc_imp <- filterVarImp(x = dataset[,7:10], y = dataset$Global_Sales)

Sort the score in decreasing order

roc_imp <- data.frame(cbind(variable = rownames(roc_imp), score = roc_imp[,1]))
roc_imp$score <- as.double(roc_imp$score)
roc_imp[order(roc_imp$score,decreasing = TRUE),]

we will remove the (JP_Sales) because it is of low importance to our class_label(Global_Sales)

dataset<- dataset[,-9]

Dataset after pre-processing:

print(dataset)
LS0tCnRpdGxlOiAiR2xvYmFsIHZpZGVvIGdhbWVzIHNhbGVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoKIyBQcm9wbGVtOgpXZSBsaXZlIGluIGEgdGltZSB3aGVuIHZpZGVvIGdhbWVzIGFyZSBleHRyZW1lbHkgcG9wdWxhci4gVGhlIGdsb2JhbCB2aWRlbyBnYW1lIG1hcmtldCBjb250aW51ZXMgdG8gZ3JvdyB5ZWFyLW9uLXllYXIsIGFuZCB0aGUgaW5kdXN0cnkgaXMgbm93IHZhbHVlZCBhdCBvdmVyICQxMDAgYmlsbGlvbiB3b3JsZHdpZGUuIFdpdGggdGVjaG5vbG9neSBjb250aW51b3VzbHkgcHVzaGluZyB0aGUgYm91bmRhcmllcywgdmlkZW8gZ2FtZXMgaGF2ZSBvbmx5IGJlY29tZSBtb3JlIHBvcHVsYXIgYW5kIG1vcmUgaGlnaC1xdWFsaXR5LiBHYW1lcGxheSBtZWNoYW5pY3MsIGN1dHRpbmctZWRnZSBncmFwaGljcywgYW5kIGludHJpY2F0ZSBzdG9yeWxpbmVzIG1ha2UgdG9kYXkncyBnYW1lcyBtb3JlIGltbWVyc2l2ZSB0aGFuIGV2ZXIgYmVmb3JlLiBXZSBjaG9zZSB0aGlzIGRhdGFzZXQgdG8gZ2FpbiBpbnNpZ2h0cyBvbiB0aGUgcG9wdWxhcml0eSBvZiBkaWZmZXJlbnQgZ2FtaW5nIHBsYXRmb3JtcyBhbmQgdGhlIG1vc3Qgc3VjY2Vzc2Z1bCBnZW5yZXMgYXNzb2NpYXRlZCB3aXRoIHRob3NlIHBsYXRmb3Jtcy4gCgojIERhdGEgTWluaW5nIFRhc2s6Ck91ciB0YXNrIGRhdGEgbWluaW5nIHRhc2sgaXMgdG8gcHJlZGljdCB0aGUgcG9wdWxhcml0eSBvZiB1cGNvbWluZyBnYW1lcyB1c2luZyByZWdyZXNzaW9uLgoKIyBEZXNjcmlwdGlvbiBvZiB0aGUgZGF0YXNldDoKClRoZSBkYXRhc2V0IHByb3ZpZGVkIGJ5IHZnY2hhcnR6LmNvbSBzdXBwbHkgdXMgd2l0aCBhIHZhbHVhYmxlIHJlc291cmNlIHRvIGV4cGxvcmUgdGhlIHBsYXRmb3JtcyBhbmQgZ2VucmVzIG9mIHRoZSB0b3AgMTY1OTkgZ2xvYmFsIHZpZGVvIGdhbWVzLiBUaHJvdWdoIGl0LCB3ZSBjYW4gYW5hbHl6ZSB0aGUgbW9zdCBwb3B1bGFyIHBsYXRmb3JtcyBhbmQgZ2VucmVzIHRoYXQgYXJlIGluZmx1ZW5jaW5nIGdsb2JhbCBzYWxlcywgYW5kIGRldGVjdHIgaG93IHJlZ2lvbnMnIHNhbGVzIGFmZmVjdCBnbG9iYWwgc2FsZXMuIAoKIyBPdXIgZ29hbDoKCk91ciBnb2FsICBmcm9tIHN0dWR5aW5nIHRoaXMgZGF0YXNldCBpcyB0byB1dGlsaXplIGNsYXNzaWZpY2F0aW9uIGFuZCBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgb24gdGhlIGlucHV0IGRhdGEgdG8gbWFrZSBwcmVkaWN0aW9ucyBhYm91dCB0aGUgcG9wdWxhcml0eSBvZiB1cGNvbWluZyBnYW1lcy4KCiMgU291cmNlIGFuZCBsaW5rOgpTb3VyY2U6IEthZ2dsZQoKVVJMIGxpbms6IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvZ3JlZ29ydXQvdmlkZW9nYW1lc2FsZXMKCgoKCiMgQXR0cmlidXRlcyBkZXNjcmlwdGlvbjoKCgp8ICoqQXR0cmlidXRlcyBuYW1lKiogfCAqKkRlc2NyaXB0aW9uKiogICAgICAgICAgICAgICAgICAgfCAqKkRhdGEgdHlwZSoqIHwgCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfFJhbmsgICAgICAgICAgICAgICB8IFJhbmtpbmcgb2YgdGhlIGdhbWUgYmFzZWQgb24gZ2xvYmFsIHNhbGVzLiB8IE51bWVyaWMgICAgICAgfAp8IE5hbWUgICAgICAgICAgICB8IE5hbWUgb2YgdGhlIGdhbWUuIHwgTm9taW5hbCAgICAgICB8IAp8IFBsYXRmb3JtICAgICAgfCBQbGF0Zm9ybSB0aGUgZ2FtZSB3YXMgcmVsZWFzZWQgb24uIHwgTm9taW5hbCAgICAgICB8IAp8IFllYXIgICAgICAgICAgICAgICB8IFllYXIgdGhlIGdhbWUgd2FzIHJlbGVhc2VkLiB8IE9yZGluYWwgICAgICAgfCAKfCBHZW5yZSAgICAgICAgICAgIHwgR2VucmUgb2YgdGhlIGdhbWUgfCBOb21pbmFsICAgICAgIHwgCnwgUHVibGlzaGVyICAgICAgfCBQdWJsaXNoZXIgb2YgdGhlIGdhbWUuIHwgTm9taW5hbCAgICAgICB8IAp8IE5BX1NhbGVzICAgICAgfCBTYWxlcyBvZiB0aGUgZ2FtZSBpbiBOb3J0aCBBbWVyaWNhIHwgTnVtZXJpYyAocmF0aW8tc2NhbGVkKSAgICAgICB8IAp8IEVVX1NhbGVzICAgICAgIHwgU2FsZXMgb2YgdGhlIGdhbWUgaW4gRXVyb3BlIHwgTnVtZXJpYyAocmF0aW8tc2NhbGVkKSAgICAgICAgfCAKfCBKUF9TYWxlcyAgICAgICAgfCBTYWxlcyBvZiB0aGUgZ2FtZSBpbiBKYXBhbiB8IE51bWVyaWMgKHJhdGlvLXNjYWxlZCkgICAgICAgIHwgCnwgT3RoZXJfU2FsZXMgfCBTYWxlcyBvZiB0aGUgZ2FtZSBpbiBvdGhlciByZWdpb25zIHwgTnVtZXJpYyAocmF0aW8tc2NhbGVkKSAgICAgICAgfCAKfCBHbG9iYWxfU2FsZXMgIHwgVG90YWwgc2FsZXMgb2YgdGhlIGdhbWUgd29ybGR3aWRlIHwgTnVtZXJpYyAocmF0aW8tc2NhbGVkKSAgICAgfCAgICAgCgoKIyBDbGFzcyBsYWJlbDoKClBvcHVsYXInIGlzIG91ciBjbGFzcyBsYWJlbCwgd2Ugd2lsbCB1c2UgR2xvYmFsX1NhbGVzIGF0dHJpYnV0ZSB0byBwcmVkaWN0IHdoZXRoZXIgYSBnYW1lIHdpbGwgc2VsbCAxMDAwMDAwIG9yIG1vcmUgZ2xvYmFsbHkuIAoKCgoKCiMgbG9hZGluZyBsaWJyYXJpZXMgbmVlZGVkIGZvciBvdXIgZGF0YSBtaW5pbmcgdGFza3M6CmBgYHtyfQpsaWJyYXJ5KG91dGxpZXJzKSAKbGlicmFyeShkcGx5cikKbGlicmFyeShIbWlzYykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KG1sYmVuY2gpCmxpYnJhcnkoY2FyZXQpCm9wdGlvbnMobWF4LnByaW50PTk5OTk5OTkpCmBgYAoKCgoKCiMgSW1wb3J0aW5nIG91ciBkYXRhc2V0OgpgYGB7cn0KZGF0YXNldD1yZWFkLmNzdigiRGF0YXNldC92Z3NhbGVzLmNzdiIpCmBgYAoKCgoKIyBHZW5lcmFsIGluZm8gYWJvdXQgb3VyIGRhdGFzZXQ6CgpjaGVraW5nIG51bWJlciBvZiByb3dzIGFuZCBjb2x1bW5zLCBhbmQgY2hla2luZyBkaW1lbnNpb25hbGl0eSBhbmQgY291bHVtbnMgbmFtZXMKYGBge3J9Cm5yb3coZGF0YXNldCkKbmNvbChkYXRhc2V0KQpkaW0oZGF0YXNldCkKbmFtZXMoZGF0YXNldCkKYGBgCgoKCgpEYXRhc2V0IHN0cnVjdHVyZToKYGBge3J9CnN0cihkYXRhc2V0KQpgYGAKCgoKc2FtcGxlIG9mIHJhdyBkYXRhc2V0KGZpcnN0IDEwIHJvd3MpOgpgYGB7cn0KaGVhZChkYXRhc2V0LCAxMCkKYGBgCgpzYW1wbGUgb2YgcmF3IGRhdGFzZXQobGFzdCAxMCByb3dzKToKYGBge3J9CnRhaWwoZGF0YXNldCwgMTApCmBgYAoKc3VtbWFyeSBvZiBvdXIgZGF0YXNldDoKYGBge3J9CnN1bW1hcnkoZGF0YXNldCkKYGBgCgp2YXJpYW5jZSBvZiBudW1lcmljIGRhdGE6CmBgYHtyfQp2YXIoZGF0YXNldCROQV9TYWxlcykKdmFyKGRhdGFzZXQkRVVfU2FsZXMpCnZhcihkYXRhc2V0JEpQX1NhbGVzKQp2YXIoZGF0YXNldCRPdGhlcl9TYWxlcykKdmFyKGRhdGFzZXQkR2xvYmFsX1NhbGVzKQpgYGAKCgoKCgoKIyBHcmFwaHM6CgpgYGB7cn0KZGF0YXNldDIgPC0gZGF0YXNldCAlPiUgc2FtcGxlX24oNTApCnRhYiA8LSBkYXRhc2V0MiRQbGF0Zm9ybSAlPiUgdGFibGUoKQpwcmVjZW50YWdlcyA8LSB0YWIgJT4lIHByb3AudGFibGUoKSAlPiUgcm91bmQoMykgKiAxMDAgCnR4dCA8LSBwYXN0ZTAobmFtZXModGFiKSwgJ1xuJywgcHJlY2VudGFnZXMsICclJykgCgpwaWUodGFiLCBsYWJlbHM9dHh0ICwgbWFpbiA9ICJQaWUgY2hhcnQgb2YgUGxhdGZvcm0iKSAKCmBgYAoKV2Ugbm90aWNlIGZyb20gdGhlIHBpZSBjaGFydCBvZiBwbGF0Zm9ybSBhdHRyaWJ1dGUgdGhhdCByZWxlYXNpbmcgYSBnYW1lIGZvciBQUyB1c2VycyB3aWxsIGluY3JlYXNlIHRoZSBwb3B1bGFyaXR5IG9mIHRoZSBnYW1lIHNpbmNlIGl0IGlzIHRoZSBtb3N0IGNvbW1vbiBwbGF0Zm9ybSBhbW9uZyBnYW1lcnMuIAoKCgoKCmBgYHtyfQojIGNvbG9yaW5nIGJhcnBsb3QgYW5kIGFkZGluZyB0ZXh0CnRhYjwtZGF0YXNldCRHZW5yZSAlPiUgdGFibGUoKSAKCnByZWNlbnRhZ2VzPC10YWIgJT4lIHByb3AudGFibGUoKSAlPiUgcm91bmQoMykqMTAwIAoKdHh0PC1wYXN0ZTAobmFtZXModGFiKSwgJ1xuJyxwcmVjZW50YWdlcywnJScpIAoKYmIgPC0gZGF0YXNldCRHZW5yZSAlPiUgdGFibGUoKSAlPiUgYmFycGxvdChheGlzbmFtZXM9RiwgbWFpbiA9ICJCYXJwbG90IGZvciBQb3B1bGFyIGdlbnJlcyAiLHlsYWI9J2NvdW50Jyxjb2w9YygncGluaycsJ2JsdWUnLCdsaWdodGJsdWUnLCdncmVlbicsJ2xpZ2h0Z3JlZW4nLCdyZWQnLCdvcmFuZ2UnLCdyZWQnLCdncmV5JywneWVsbG93JywnYXp1cmUnLCdvbGl2ZWRyYWInKSkgCgp0ZXh0KGJiLHRhYi8yLGxhYmVscz10eHQsY2V4PTEuNSkgCmBgYApJbiB0ZXJtcyBvZiBnZW5yZSwgYWN0aW9uIGdhbWVzIGFyZSB0aGUgbW9zdCBwb3B1bGFyLCBmb2xsb3dlZCBieSBzcG9ydHMgYW5kIG11c2ljIGdhbWVzLiBJdCBpcyBzYWZlIHRvIGFzc3VtZSB0aGF0IGEgaGlnaCBudW1iZXIgb2YgZ2VucmVzIG9mIHRoaXMgbmF0dXJlIGV4aXN0IGR1ZSB0byB0aGVpciBwb3B1bGFyaXR5IGFuZCBzYWxlcy4KCgoKCgpgYGB7cn0KYm94cGxvdChkYXRhc2V0JE5BX1NhbGVzICwgbWFpbj0iCkJveFBsb3QgZm9yIE5BX1NhbGVzIikKYGBgClRoZSBib3hwbG90IG9mIHRoZSBOQV9TYWxlcyAgKFNhbGVzIG9mIHRoZSBnYW1lIGluIG5vcnRoIEFtZXJpY2EpIGF0dHJpYnV0ZSBpbmRpY2F0ZXMgdGhhdCB0aGUgdmFsdWVzIGFyZSBjbG9zZSB0byBlYWNoIG90aGVyICxhbmQgdGhlcmUgYXJlIGEgbG90IG9mIG91dGxpZXJzIHNpbmNlIHRoZSBkYXRhc2V0IHJlcHJlc2VudHMgYWxsIHRoZSBub3J0aCBBbWVyaWNhIHNhbGVzIG9mIHZpZGVvIGdhbWVzLgoKYGBge3J9CmJveHBsb3QoZGF0YXNldCRFVV9TYWxlcywgbWFpbj0iCiBCb3hQbG90IGZvciBFVV9TYWxlcyIpCmBgYApUaGUgYm94cGxvdCBvZiB0aGUgRVVfU2FsZXMgKHNhbGVzIG9mIHRoZSBnYW1lIGluIEV1cm9wZSkgYXR0cmlidXRlIGluZGljYXRlcyB0aGF0IHRoZSB2YWx1ZXMgYXJlIGNsb3NlIHRvIGVhY2ggb3RoZXIsIGFuZCB0aGVyZSBhcmUgYSBsb3Qgb2Ygb3V0bGllcnMgc2luY2UgdGhlIGRhdGFzZXQgcmVwcmVzZW50cyBhbGwgdGhlIEV1cm9wZSBzYWxlcyBvZiB2aWRlbyBnYW1lcy4KCmBgYHtyfQpib3hwbG90KGRhdGFzZXQkSlBfU2FsZXMgLCBtYWluPSIKIEJveFBsb3QgZm9yIEpQX1NhbGVzIikKYGBgClRoZSBib3hwbG90IG9mIHRoZSBKUF9TYWxlcyAoc2FsZXMgb2YgdGhlIGdhbWUgaW4gSmFwYW4pIGF0dHJpYnV0ZSBpbmRpY2F0ZXMgdGhhdCB0aGUgdmFsdWVzIGFyZSBjbG9zZSB0byBlYWNoIG90aGVyLCBhbmQgdGhlcmUgYXJlIGEgbG90IG9mIG91dGxpZXJzIHNpbmNlIHRoZSBkYXRhc2V0IHJlcHJlc2VudHMgYWxsIHRoZSBKYXBhbiBzYWxlcyBvZiB2aWRlbyBnYW1lcy4KCgpgYGB7cn0KYm94cGxvdChkYXRhc2V0JE90aGVyX1NhbGVzICwgbWFpbj0iCiBCb3hQbG90IGZvciBPdGhlcl9TYWxlcyIpIApgYGAgIAoKVGhlIGJveHBsb3Qgb2YgdGhlIE90aGVyLXNhbGVzIGF0dHJpYnV0ZSBpbmRpY2F0ZSB0aGF0IHRoZSB2YWx1ZXMgYXJlIGNsb3NlIHRvIGVhY2ggb3RoZXIgLGFuZCB0aGVyZSBpcyBhIGxvdCBvZiBvdXRsaWVycyBzaW5jZSB0aGUgZGF0YXNldCByZXByZXNlbnRzIHRoZSBnbG9iYWwgc2FsZXMgb2YgdmlkZW8gZ2FtZXMuIAoKCgoKYGBge3J9CmJveHBsb3QoZGF0YXNldCRHbG9iYWxfU2FsZXMgLCBtYWluPSJCb3hQbG90IGZvciBHbG9iYWxfU2FsZXMiKQoKYGBgICAKVGhlIGJveHBsb3Qgb2YgdGhlIEdsb2JhbC1zYWxlcyBhdHRyaWJ1dGUgaW5kaWNhdGUgdGhhdCB0aGUgdmFsdWVzIGFyZSBjbG9zZSB0byBlYWNoIG90aGVyICxhbmQgdGhlcmUgaXMgYSBsb3Qgb2Ygb3V0bGllcnMgc2luY2UgdGhlIGRhdGFzZXQgcmVwcmVzZW50cyB0aGUgZ2xvYmFsIHNhbGVzIG9mIHZpZGVvIGdhbWVzLiAKCgoKYGBge3J9CnFwbG90KGRhdGEgPSBkYXRhc2V0LCB4PUdsb2JhbF9TYWxlcyx5PUdlbnJlLGZpbGw9SSgieWVsbG93Iiksd2lkdGg9MC41ICxnZW9tID0gImJveHBsb3QiICwgbWFpbiA9ICJCb3hQbG90cyBmb3IgZ2VucmUgYW5kIEdsb2JhbF9TYWxlcyIpCmBgYAoKSW4gdGhlIGJveHBsb3Qgd2UgY2FuIHNlZSB0aGF0IGFsbCB0aGUgZ2VucmVzIGhhdmUgR2xvYl8gc2FsZXMgY2xvc2UgdG8gZWFjaCBvdGhlciwgYnV0IHdlIG5vdGljZSBhbiBvdXRsaWVyIHRoYXQgcmVhY2hlcyBtb3JlIHRoYW4gODAgR2xvYl8gc2FsZXMgd2hpY2ggaXMgYSBnYW1lIHdpdGggZ2VucmUgc3BvcnRzLiAKCmBgYHtyfQpkYXRhc2V0JFllYXIgJT4lIHRhYmxlKCkgJT4lIGJhcnBsb3QoIG1haW4gPSAiQmFycGxvdCBmb3IgeWVhciIpCmBgYAoKQnkgdGhlIGJhcnBsb3Qgd2UgY2FuIHNlZSB0aGF0IHRoZSBudW1iZXIgb2YgdmlkZW8gZ2FtZXMgd2VyZSBsb3cgZnJvbSAxOTgwIHllYXIgdW50aWwgdGhlIDIwMDAgbnVtYmVycyBvZiBnYW1lcyBncm93IHRvIG1vcmUgdGhhbiAxMjAwIHRpbGwgMjAxMi4KCgpgYGB7cn0KcGFpcnMofk5BX1NhbGVzICsgRVVfU2FsZXMgKyBKUF9TYWxlcyArIE90aGVyX1NhbGVzICsgR2xvYmFsX1NhbGVzLCBkYXRhID0gZGF0YXNldCwKICAgICAgbWFpbiA9ICJTYWxlcyBTY2F0dGVycGxvdCIpCmBgYCAgICAKV2UgdXNlZCBTY2F0dGVycGxvdCB0byBkZXRlcm1pbmUgdGhlIHR5cGUgb2YgY29ycmVsYXRpb24gd2UgaGF2ZSBiZXR3ZWVuIHRoZSBzYWxlczsgd2UgY2FuIHNlZSB0aGF0IHRoZSBtYWpvcml0eSBoYXZlIHBvc2l0aXZlIGNvcnJlbGF0aW9uIHdpdGggZWFjaCBvdGhlci4gCiAKIAogICAgICAKIyAoUHJlIC0gcHJvY2Vzc2luZyk6CgojIFZhcmFpYmxlIHRyYW5zZm9ybWF0aW9uOgpgYGB7cn0KZGF0YXNldCRSYW5rPWFzLmNoYXJhY3RlcihkYXRhc2V0JFJhbmspCmBgYApXZSB0cmFuc2Zvcm1lZCB0aGUgUmFuayBmcm9tIG51bXJpYyB0byBjaGFyLGJlY2F1c2Ugd2Ugd2lsbCB1c2UgdGhlbSBhcyBvcmRpbmFsIGRhdGEuCgojIE51bGwgY2hlY2tpbmc6CndlIGNoZWNrZWQgbnVsbHMgdmFsdWVzIHRvIGtub3cgaG93IG1hbnkgbnVsbHMgdmFsdWVzIHdlIGhhdmUsIHNvIHdlIGNhbiBkZXRlcm1pbmUgaG93IHdlIHdpbGwgZGVhbCB3aXRoIHRoZW0uCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCRSYW5rKSkKTnVsbFJhbms8LWRhdGFzZXRbZGF0YXNldCRSYW5rPT0iTi9BIixdCk51bGxSYW5rCmBgYApjaGVja2luZyBmb3IgbnVsbHMgaW4gUmFuayAodGhlcmUgaXMgbm8gbnVsbHMpCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCROYW1lKSkKTnVsbE5hbWU8LWRhdGFzZXRbZGF0YXNldCROYW1lPT0iTi9BIixdCk51bGxOYW1lCmBgYAoKY2hlY2tpbmcgZm9yIG51bGxzIGluIG5hbWUgKHRoZXJlIGlzIG5vIG51bGxzKQoKYGBge3J9CnN1bShpcy5uYShkYXRhc2V0JFBsYXRmb3JtKSkKTnVsbFBsYXRmb3JtPC1kYXRhc2V0W2RhdGFzZXQkUGxhdGZvcm09PSJOL0EiLF0KCgpgYGAKY2hlY2tpbmcgZm9yIG51bGxzIGluIFBsYXRmb3JtKHRoZXJlIGlzIG5vIG51bGxzKQoKYGBge3J9CnN1bShpcy5uYShkYXRhc2V0JFllYXIpKQpOdWxsWWVhcjwtZGF0YXNldFtkYXRhc2V0JFllYXI9PSJOL0EiLF0KTnVsbFllYXIKYGBgCmNoZWNraW5nIGZvciBudWxscyBpbiB5ZWFyCndlIHdvbid0IGRlbGV0ZSB0aGUgbnVsbCBhbmQgd2Ugd2lsbCBsZWF2ZSB0aGVtIGFzIGdsb2JhbCBjb25zdGFudCBiZWNhdXNlIHdlIHdhbnQgdGhlIHNhbGVzIGRhdGEgb3V0IG9mIHRoZW0uCgpgYGB7cn0Kc3VtKGlzLm5hKGRhdGFzZXQkR2VucmUpKQpOdWxsR2VucmU8LWRhdGFzZXRbZGF0YXNldCRHZW5yZT09Ik4vQSIsXQpOdWxsR2VucmUKYGBgCmNoZWNraW5nIGZvciBudWxscyBpbiBHZW5yZSh0aGVyZSBpcyBubyBudWxscykKCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCRQdWJsaXNoZXIpKQpOdWxsUHVibGlzaGVyPC1kYXRhc2V0W2RhdGFzZXQkUHVibGlzaGVyPT0iTi9BIixdCk51bGxQdWJsaXNoZXIKYGBgCmNoZWNraW5nIGZvciBudWxscyBpbiBQdWJsaXNoZXIuCndlIHdvbid0IGRlbGV0ZSB0aGUgbnVsbCBhbmQgd2Ugd2lsbCBsZWF2ZSB0aGVtIGFzIGdsb2JhbCBjb25zdGFudCBhcyBpdCBpcyBiZWNhdXNlIHdlIHdhbnQgdGhlIHNhbGVzIGRhdGEgb2YgdGhlbS4KCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCROQV9TYWxlcykpCk51bGxOQV9TYWxlczwtZGF0YXNldFtkYXRhc2V0JE5BX1NhbGVzPT0iTi9BIixdCk51bGxOQV9TYWxlcwpgYGAKY2hlY2tpbmcgZm9yIG51bGxzIGluIE5BX1NhbGVzICh0aGVyZSBpcyBubyBudWxscykKCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCRFVV9TYWxlcykpCk51bGxFVV9TYWxlczwtZGF0YXNldFtkYXRhc2V0JEVVX1NhbGVzPT0iTi9BIixdCk51bGxFVV9TYWxlcwpgYGAKY2hlY2tpbmcgZm9yIG51bGxzIGluIEVVX1NhbGVzICh0aGVyZSBpcyBubyBudWxscykKCmBgYHtyfQpzdW0oaXMubmEoZGF0YXNldCRKUF9TYWxlcykpCk51bGxKUF9TYWxlczwtZGF0YXNldFtkYXRhc2V0JEpQX1NhbGVzPT0iTi9BIixdCk51bGxKUF9TYWxlcwpgYGAKY2hlY2tpbmcgZm9yIG51bGxzIGluIEpQX1NhbGVzICh0aGVyZSBpcyBubyBudWxscykKCgpgYGB7cn0Kc3VtKGlzLm5hKGRhdGFzZXQkT3RoZXJfU2FsZXMpKQpOdWxsT3RoZXJfU2FsZXM8LWRhdGFzZXRbZGF0YXNldCRPdGhlcl9TYWxlcz09Ik4vQSIsXQoKCmBgYApUaGVyZSBpcyBubyBudWxsIHZhbHVlcyBpbiB0aGUgb3RoZXJfc2FsZXMuCgpgYGB7cn0Kc3VtKGlzLm5hKGRhdGFzZXQkR2xvYmFsX1NhbGVzKSkKTnVsbEdsb2JhbF9TYWxlczwtZGF0YXNldFtkYXRhc2V0JEdsb2JhbF9TYWxlc3M9PSJOL0EiLF0KCgpgYGAKVGhlcmUgaXMgbm8gbnVsbCB2YWx1ZXMgaW4gdGhlIEdsb2JhbF9TYWxlcy4KCiMgRW5jb2Rpbmc6CldlIHdpbGwgZW5jb2RlIG91ciBjYXRlZ29yaWNhbCBkYXRhIHNpbmNlIG1vc3QgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHdvcmsgd2l0aCBudW1iZXJzIHJhdGhlciB0aGFuIHRleHQuCgpgYGB7cn0KZGF0YXNldCRQbGF0Zm9ybT1mYWN0b3IoZGF0YXNldCRQbGF0Zm9ybSxsZXZlbHM9YygiMjYwMCIsIjNETyIsIjNEUyIsIkRDIiwiRFMiLCJHQiIsIkdCQSIsIkdDIiwiR0VOIiwiR0ciLCJONjQiLCJORVMiLCJORyIsIlBDIiwiUENGWCIsIlBTIiwiUFMyIiwiUFMzIiwiUFM0IiwiUFNQIiwiUFNWIiwiU0FUIiwiU0NEIiwiU05FUyIsIlRHMTYiLCJXaWkiLCJXaWlVIiwiV1MiLCJYMzYwIiwiWEIiLCJYT25lIiksIGxhYmVscz1jKDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxLDIyLDIzLDI0LDI1LDI2LDI3LDI4LDI5LDMwLDMxKSkKYGBgCnRoaXMgY29sdW1uIHdpbGwgYmUgZW5jb2RlZCB0byBmYWNpbGl0YXRlIG91ciBkYXRhIG1pbmluZyB0YXNrLgoKYGBge3J9CmRhdGFzZXQkR2VucmU9ZmFjdG9yKGRhdGFzZXQkR2VucmUsbGV2ZWxzPWMoIkFjdGlvbiIsIkFkdmVudHVyZSIsIkZpZ2h0aW5nIiwiUGxhdGZvcm0iLCJQdXp6bGUiLCJSYWNpbmciLCJSb2xlLVBsYXlpbmciLCJTaG9vdGVyIiwiU2ltdWxhdGlvbiIsIlNwb3J0cyIsIlN0cmF0ZWd5IiwiTWlzYyIpLGxhYmVscz1jKDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyKSkKYGBgClNpbmNlIG1vc3QgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIHdvcmsgd2l0aCBudW1iZXJzIGFuZCBub3Qgd2l0aCB0ZXh0IG9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgdGhpcyBjb2x1bW4gd2lsbCBiZSBlbmNvZGVkIHRvIGZhY2lsaXRhdGUgb3VyIGRhdGEgbWluaW5nIHRhc2suCgojIE91dGxpZXJzOgpBbmFseXNlcyBhbmQgc3RhdGlzdGljYWwgbW9kZWxzIGNhbiBiZSBydWluZWQgYnkgb3V0bGllcnMsIG1ha2luZyBpdCBkaWZmaWN1bHQgdG8gZGV0ZWN0IGEgdHJ1ZSBlZmZlY3QuIFRoZXJlZm9yZSwgd2UgYXJlIGNoZWNraW5nIGZvciB0aGVtIGFuZCByZW1vdmluZyB0aGVtIGlmIHdlIGZpbmQgYW55LgoKb3V0bGllciBvZiBOQV9TYWxlcwpgYGB7cn0KT3V0TkFfU2FsZXMgPSBvdXRsaWVyKGRhdGFzZXQkTkFfU2FsZXMsIGxvZ2ljYWwgPVRSVUUpCnN1bShPdXROQV9TYWxlcykKRmluZF9vdXRsaWVyID0gd2hpY2goT3V0TkFfU2FsZXMgPT1UUlVFLCBhcnIuaW5kID0gVFJVRSkKCmBgYApvdXRsaWVyIG9mIEVVX1NhbGVzCmBgYHtyfQpPdXRFVV9TYWxlcyA9IG91dGxpZXIoZGF0YXNldCRFVV9TYWxlcywgbG9naWNhbCA9VFJVRSkKc3VtKE91dEVVX1NhbGVzKQpGaW5kX291dGxpZXIgPSB3aGljaChPdXRFVV9TYWxlcyA9PVRSVUUsIGFyci5pbmQgPSBUUlVFKQpgYGAKb3V0bGllciBvZiBKUF9TYWxlcwpgYGB7cn0KT3V0SlBfU2FsZXMgPSBvdXRsaWVyKGRhdGFzZXQkSlBfU2FsZXMsIGxvZ2ljYWwgPVRSVUUpCnN1bShPdXRKUF9TYWxlcykKRmluZF9vdXRsaWVyID0gd2hpY2goT3V0SlBfU2FsZXMgPT1UUlVFLCBhcnIuaW5kID0gVFJVRSkKYGBgCgpvdXRsaWVyIG9mIG90aGVyX3NhbGVzIApgYGB7cn0KT3V0T1M9b3V0bGllcihkYXRhc2V0JE90aGVyX1NhbGVzLCBsb2dpY2FsPVRSVUUpICAKc3VtKE91dE9TKSAgCkZpbmRfb3V0bGllcj13aGljaChPdXRPUz09VFJVRSwgYXJyLmluZD1UUlVFKSAgCgpgYGAKCgpvdXRsaWVyIG9mIEdsb2JhbF9zYWxlcyAKCmBgYHtyfQpPdXRHUz1vdXRsaWVyKGRhdGFzZXQkR2xvYmFsX1NhbGVzLCBsb2dpY2FsPVRSVUUpICAKc3VtKE91dEdTKSAgCkZpbmRfb3V0bGllcj13aGljaChPdXRHUz09VFJVRSwgYXJyLmluZD1UUlVFKSAgCgpgYGAKCgoKIyBSZW1vdmUgb3V0bGllcnMgCmBgYHtyfQpkYXRhc2V0PSBkYXRhc2V0Wy1GaW5kX291dGxpZXIsXQpgYGAKCgoKIyBOb3JtYWxpemF0aW9uOgpUaGUgbm9ybWFsaXphdGlvbiBvZiBkYXRhIHdpbGwgaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbWFueSBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgYnkgYWNjb3VudGluZyBmb3IgZGlmZmVyZW5jZXMgaW4gdGhlIHNjYWxlIG9mIHRoZSBpbnB1dCBmZWF0dXJlcy4KCkRhdGFzZXQgYmVmb3JlIG5vcm1hbGl6YXRpb246CmBgYHtyfQpkYXRzZXRXaXRob3V0Tm9ybWFsaXphdGlvbjwtZGF0YXNldApgYGAKCgpgYGB7cn0Kbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgpIHtyZXR1cm4gKCh4IC0gbWluKHgpKSAvIChtYXgoeCkgLSBtaW4oeCkpKX0KZGF0YXNldCROQV9TYWxlczwtbm9ybWFsaXplKGRhdHNldFdpdGhvdXROb3JtYWxpemF0aW9uJE5BX1NhbGVzKQpkYXRhc2V0JEVVX1NhbGVzPC1ub3JtYWxpemUoZGF0c2V0V2l0aG91dE5vcm1hbGl6YXRpb24kRVVfU2FsZXMpCmRhdGFzZXQkSlBfU2FsZXM8LW5vcm1hbGl6ZShkYXRzZXRXaXRob3V0Tm9ybWFsaXphdGlvbiRKUF9TYWxlcykKZGF0YXNldCRPdGhlcl9TYWxlczwtbm9ybWFsaXplKGRhdHNldFdpdGhvdXROb3JtYWxpemF0aW9uJE90aGVyX1NhbGVzKQpkYXRhc2V0JEdsb2JhbF9TYWxlczwtbm9ybWFsaXplKGRhdHNldFdpdGhvdXROb3JtYWxpemF0aW9uJEdsb2JhbF9TYWxlcykKYGBgCldlIGNob3NlIG1pbi1tYXggbm9ybWFsaXphdGlvbiBpbnN0ZWFkIG9mIHotc2NvcmUgbm9ybWFsaXphdGlvbiBiZWNhdXNlIG1pbi1tYXggdHJhbnNmb3JtIHRoZSBkYXRhIGludG8gYSBzcGVjaWZpYyByYW5nZSwgd2hpY2ggZW5oYW5jZXMgaXRzIHN1aXRhYmlsaXR5IGZvciB2aXN1YWxpemF0aW9uIGFuZCBjb21wYXJpc29uLiBBZGRpdGlvbmFsbHksIGl0IHNpbXBsaWZpZXMgdGhlIHByb2Nlc3Mgb2YgYXNzZXNzaW5nIGF0dHJpYnV0ZSBpbXBvcnRhbmNlIGFuZCB0aGVpciBjb250cmlidXRpb25zIHRvIHRoZSBtb2RlbC4KCgoKCgojIEZlYXV0cmUgc2VsZWN0aW9uOgoKT3VyIGNsYXNzIGxhYmVsIChwb3B1bGFyKSByZWZlcnMgdG8gR2xvYmFsX1NhbGVzLmJlY2F1c2Ugd2UgaGF2ZSBtdWx0aXBsZSByZWdpb25zIHNhbGVzIHdlIGNob3NlIHRvIGV2YWx1YXRlIGVhY2ggcmVnaW9uIHNhbGVzIGJhc2VkIG9uIHRoZWlyIGltcG9ydGFuY2UgdG8gKGdsb2JhbF9zYWxlcykgY29sdW1uLGFuZCB0aG9zZSB0aGF0IGFyZSBsZXNzIGltcG9ydGFudCB3aWxsIGJlIGRlbGV0ZWQgZnJvbSB0aGUgZGF0YXNldC4KCgpVc2Ugcm9jX2N1cnZlIGFyZWEgYXMgc2NvcmUKYGBge3J9CnJvY19pbXAgPC0gZmlsdGVyVmFySW1wKHggPSBkYXRhc2V0Wyw3OjEwXSwgeSA9IGRhdGFzZXQkR2xvYmFsX1NhbGVzKQpgYGAKCgpTb3J0IHRoZSBzY29yZSBpbiBkZWNyZWFzaW5nIG9yZGVyCmBgYHtyfQpyb2NfaW1wIDwtIGRhdGEuZnJhbWUoY2JpbmQodmFyaWFibGUgPSByb3duYW1lcyhyb2NfaW1wKSwgc2NvcmUgPSByb2NfaW1wWywxXSkpCnJvY19pbXAkc2NvcmUgPC0gYXMuZG91YmxlKHJvY19pbXAkc2NvcmUpCnJvY19pbXBbb3JkZXIocm9jX2ltcCRzY29yZSxkZWNyZWFzaW5nID0gVFJVRSksXQpgYGAKCgp3ZSB3aWxsIHJlbW92ZSB0aGUgKEpQX1NhbGVzKSBiZWNhdXNlIGl0IGlzIG9mIGxvdyBpbXBvcnRhbmNlIHRvIG91ciBjbGFzc19sYWJlbChHbG9iYWxfU2FsZXMpCmBgYHtyfQpkYXRhc2V0PC0gZGF0YXNldFssLTldCmBgYAoKIyBEYXRhc2V0IGFmdGVyIHByZS1wcm9jZXNzaW5nOgpgYGB7cn0KcHJpbnQoZGF0YXNldCkKYGBgCgo=